package com.example.librarymanagementproject2.service.impl;

import com.example.librarymanagementproject2.Common.config.RabbitConfig;
import com.example.librarymanagementproject2.Common.exception.GlobalException;
import com.example.librarymanagementproject2.mapper.BookMapper;
import com.example.librarymanagementproject2.mapper.BorrowRecordMapper;
import com.example.librarymanagementproject2.mapper.UserMapper;
import com.example.librarymanagementproject2.model.document.BorrowRecordDocument;
import com.example.librarymanagementproject2.model.entity.Book;
import com.example.librarymanagementproject2.model.entity.BorrowRecord;
import com.example.librarymanagementproject2.model.entity.User;
import com.example.librarymanagementproject2.model.enums.BorrowStatus;
import com.example.librarymanagementproject2.service.BorrowRecordService;
import com.example.librarymanagementproject2.service.UserService;
import com.example.librarymanagementproject2.service.enums.BookEnum;
import com.example.librarymanagementproject2.service.enums.EmailEnum;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * @ClassName BorrowRecordServiceImpl
 * @Description TODO
 * @Author Kx
 * @Date 2025/10/28 19:20
 * @Version 1.0
 */
@Service
public class BorrowRecordServiceImpl implements BorrowRecordService {
    @Autowired
    private BorrowRecordMapper borrowRecordMapper;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private CacheManager cacheManager;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private BookMapper bookMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserService userService;

    /**
     * @author: kx
     * @date: 2025/10/28 9:58
     * @param borrowRecord : 借阅记录信息
     * @description: 借阅图书
     */
    @Transactional
    @Override
    public void checkoutBook(BorrowRecord borrowRecord) {
        try {
            // 检查用户借阅数量是否超出限制
            if (!checkUserBorrowLimit(borrowRecord)) {
                throw new GlobalException(500, "您借书的数量已达到最大");
            }

            // 判断用户是否借了同一本书
            if (checkDuplicateBookBorrow(borrowRecord)) {
                throw new GlobalException(500, "您已经借了这本书");
            }

            // 使用 lua 脚本实现库存的扣减
            decrStock(borrowRecord.getBookId());
        } catch (Exception e) {
            // 回滚库存
            redisTemplate.opsForValue().increment(BookEnum.BOOK_STOCK_PREFIX.value() + borrowRecord.getBookId());
            throw new GlobalException(500, "借阅图书失败");
        }

        // 添加图书借阅信息
        Long borrowId = insertBorrowRecord(borrowRecord);

        // 延迟投递消息
        sendDelayMessage(borrowId);
    }

    /**
     * @author: kx
     * @date: 2025/10/29 19:36
     * @param borrowRecord : 借阅记录信息
     * @return boolean
     * @description: 检查用户是否已经借了这本书
    */
    private boolean checkDuplicateBookBorrow(BorrowRecord borrowRecord) {
        // 获取该用户的所有借阅记录
        List<BorrowRecordDocument> borrowRecordDocuments = userService.getBorrowRecordsByUserId(borrowRecord.getUserId());

        if (borrowRecordDocuments == null || borrowRecordDocuments.isEmpty()) {
            return false;
        }

        // 判断该用户是否已经借了这本书(true表示存在，false表示不存在)
        return borrowRecordDocuments.stream().anyMatch(borrowRecordDocument -> borrowRecord.getBookId().equals(borrowRecordDocument.getBookId()) && BorrowStatus.BORROWED.name().equals(borrowRecordDocument.getStatus()));
    }

    /**
     * @author: kx
     * @date: 2025/10/29 19:20
     * @param borrowRecord : 借阅记录信息
     * @return boolean
     * @description: 检查用户借阅数量是否超出限制
    */
    private boolean checkUserBorrowLimit(BorrowRecord borrowRecord) {
        // 获取该用户信息
        User user = userMapper.getUserById(borrowRecord.getUserId());
        if (user == null) {
            throw new GlobalException(500, "用户不存在");
        }
        // 获取该用户的所有借阅记录
        List<BorrowRecordDocument> borrowRecordDocuments = userService.getBorrowRecordsByUserId(borrowRecord.getUserId());
        // 获取该用户已经借出了几本书
        long count = borrowRecordDocuments.stream().filter(borrowRecordDocument -> BorrowStatus.BORROWED.name().equals(borrowRecordDocument.getStatus())).count();

        return count < user.getMaxBorrowNum();
    }

    /**
     * @author: kx
     * @date: 2025/10/28 10:27
     * @param bookId : 图书ID
     * @description: 扣减图书库存
     */
    private void decrStock(Long bookId) {
        // 先判断本地缓存中是否有标记过此商品，如果标记了表示此商品已售完
        Cache localCache = cacheManager.getCache("localCache");
        if (localCache.get(BookEnum.BOOK_STOCK_PREFIX.value() + bookId) != null) {
            throw new GlobalException(500, "此商品已售完");
        }

        // 判断缓存中是否有此商品库存信息
        Object object = redisTemplate.opsForValue().get(BookEnum.BOOK_STOCK_PREFIX.value() + bookId);
        if (object == null) {
            // 缓存中没有此商品库存信息，则从数据库中获取
            Book book = bookMapper.getBookById(bookId);
            if (book == null) {
                throw new GlobalException(500, "图书不存在");
            }
            if (book.getTotalNum() <= 0) {
                throw new GlobalException(500, "此商品已售完");
            }
            // 把库存缓存到Redis中
            redisTemplate.opsForValue().set(BookEnum.BOOK_STOCK_PREFIX.value() + bookId, book.getTotalNum());
        }

        // 创建 lua 脚本需要的 keys 集合
        // 由于 Redis 引擎接收 key 是一个集合，因此只有一个 key 也要封装为集合对象
        List<String> keys = Collections.singletonList(BookEnum.BOOK_STOCK_PREFIX.value() + bookId);
        // 创建 RedisScript(Redis 脚本执行器)并设置 lua 脚本的文件位置
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/stock.lua")));
        // 设置返回结果类型为 Long
        redisScript.setResultType(Long.class);
        // 执行 lua 脚本，返回库存剩余数量
        Long stock = redisTemplate.execute(redisScript, keys);
        if (stock <= 0) {
            // 库存已售完，则将此商品标记为已售完
            localCache.put(BookEnum.BOOK_STOCK_PREFIX.value() + bookId, true);
            throw new GlobalException(500, "此商品已售完");
        }
    }

    /**
     * @author: kx
     * @date: 2025/10/29 9:35
     * @param borrowRecord : 借阅记录信息
     * @return java.lang.Long
     * @description: 添加图书借阅信息
    */
    private Long insertBorrowRecord(BorrowRecord borrowRecord) {
        // 添加图书借阅信息并返回借阅信息id
        try {
            return borrowRecordMapper.insertBorrowRecord(borrowRecord);
        } catch (Exception e) {
            throw new GlobalException(500, "添加图书借阅信息失败");
        }
    }

    /**
     * @author: kx
     * @date: 2025/10/28 11:52
     * @param BorrowId : 借阅记录ID
     * @description: 发送延迟消息
     */
    private void sendDelayMessage(Long BorrowId) {
        // 创建消息唯一ID
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        // 延迟发送
        rabbitTemplate.convertAndSend(RabbitConfig.BORROW_RECORD_SUCCESS_DELAY_EXCHANGE, RabbitConfig.BORROW_RECORD_SUCCESS_DELAY_ROUTING_KEY, BorrowId, messageSourceProcessor -> {
            // 设置延迟时间
            messageSourceProcessor.getMessageProperties().setDelayLong(600000L); // 10 分钟
            // 返回消息处理器
            return messageSourceProcessor;
        }, correlationData);
    }

    /**
     * @author: kx
     * @date: 2025/10/29 11:14
     * @param verifyCode : 验证码
     * @param userId : 用户ID
     * @description: 验证码校验并修改借阅状态
    */
    @Override
    public void verifyCodeAndUpdateStatus(String verifyCode, Long userId) {
        // 获取用户信息
        User user = userMapper.getUserById(userId);
        if (user == null) {
            throw new GlobalException(500, "用户不存在");
        }
        // 从缓存中获取验证码和借阅记录Id
        Map<String, Object> object = (Map<String, Object>) redisTemplate.opsForValue().get(EmailEnum.CAPTCHA_PREFIX.value() + user.getEmail());
        // 判断是否从缓存中获取数据
        if (object != null && object.isEmpty()) {
            throw new GlobalException(500, "验证码已过期");
        }
        // 获取验证码
        String code = (String) object.get("code");
        // 获取借阅记录Id
        Long borrowId = (Long) object.get("borrowId");

        // 判断验证码是否正确
        if (!code.equals(verifyCode)) {
            throw new GlobalException(500, "验证码错误");
        }

        BorrowRecord borrowRecord = new BorrowRecord();
        // 设置id
        borrowRecord.setId(borrowId);
        // 设置状态
        borrowRecord.setStatus(BorrowStatus.BORROWED);
        // 设置应还日期(往后推迟七天)
        borrowRecord.setDueDate(new Date(System.currentTimeMillis() + 7L * 24 * 60 * 60 * 1000));
        // 修改借阅状态
        borrowRecordMapper.updateBorrowRecordStatus(borrowRecord);
    }

    /**
     * @author: kx
     * @date: 2025/10/28 19:22
     * @param borrowRecordId : 借阅图书id
     * @description: 处理图书归还
    */
    @Override
    public void processBookReturn(Long borrowRecordId) {
        // 获取借阅信息
        BorrowRecord borrowRecord = borrowRecordMapper.getBorrowRecordById(borrowRecordId);
        if (borrowRecord == null) {
            throw new GlobalException(500, "借阅信息不存在");
        }
        // 回填缓存库存
        redisTemplate.opsForValue().increment(BookEnum.BOOK_STOCK_PREFIX.value() + borrowRecord.getBookId());

        // 设置为归还状态
        borrowRecord.setStatus(BorrowStatus.RETURNED);
        try {
            // 归还图书时修改借阅状态
            borrowRecordMapper.updateBorrowRecordStatusOnReturn(borrowRecord);
        } catch (Exception e) {
            throw new GlobalException(500, "修改借阅状态失败");
        }
    }
}
